package des.algorithm;

import des.output.Output;

/* Hoofdklasse voor de DES encryptie/decryptie */
public class DES extends Encryption {
	private Key key; // the used key

	/* Main constructor */
	public DES() {
		super();
	}

	/* Constructor met een 64bits key als input */
	public DES(long originalKey) {
		super();
		key = new Key(originalKey);

	}

	/* Methode om een 64 bits waarde te encoderen */
	public long encryptLong(long bits) {
		Output.printTitle("Encoding process:");
		status = Status.ENCRYPTING;
		return mainProcess(bits);
	}

	/* Methode om een 64 bits waarde te encoderen met de gegeven key */
	public long encodeLong(long bits, Key key) {
		this.key = key;
		return encryptLong(bits);
	}

	/* Methode om een 64 bits waarde te decoderen */
	public long decryptLong(long bits) {
		Output.printTitle("Decoding process:");
		status = Status.DECRYPTING;
		return mainProcess(bits);
	}

	/* Methode om een 64 bits waarde te decodere nmet de gegeven key */
	public long decodeLong(long bits, Key key) {
		this.key = key;
		return decryptLong(bits);
	}

	/*
	 * Hoofdproces: Heeft een 64 bit waarde (long) als input en een gedoceerde
	 * 64 bit waarde (long) als output
	 */
	private long mainProcess(long bits) {
		Output.printLong(bits, "Original long");

		// initial permutation
		bits = doIP(bits);

		Output.printLong(bits, "After IP");

		// isoleren linker (32) bits en rechter (32) bits
		int l, r;
		// linkerhelft: 32 bit shift naar rechts om de rechter helft er af te
		// gooien
		l = (int) (bits >> 32);
		// rechterhelft: de 64 bit long naar een 32 bit int casten zodat enkel
		// de laatste 32 bits behouden worden
		r = (int) bits;

		// iteratie voor de 16 tussenstappen
		for (int i = 0; i < 16; i++) {
			Output.printInt(l, "L" + (i + 1));
			Output.printInt(r, "R" + (i + 1));

			long subkey = 0;
			// De nodige subkey wordt opgevraagd
			// Indien encryptie van 0 naar 15
			// Indien decryptie van 15 naar 0
			if (status == Status.ENCRYPTING) {
				subkey = key.getSubkey(i);
			} else if (status == Status.DECRYPTING) {
				subkey = key.getSubkey(15 - i);
			}

			// de oude linker helft wordt bewaard
			int old_l = l;

			// de nieuwe linker helft wordt de oude rechterhelft
			l = r;

			// de nieuwe rechterhelft wordt berekend.
			// bitwise XOR tussen de oude linkerhelft en het resultaat van de
			// feistelfunctie
			r = old_l ^ function(r, subkey);
		}

		// reverse the two 32-bit segments (left to right; right to left)
		bits = (r & 0xFFFFFFFFL) << 32 | (l & 0xFFFFFFFFL);

		// apply the final permutation
		bits = doFP(bits);

		Output.printLong(bits, "Final");
		return bits;

	}

	/*
	 * Feiste functie Voert alle nodige stappen van de feistel functie uit.
	 * Heeft als invoer een 32 bit int en een 48 bits subkey De uitvoer is een
	 * 48 bit long
	 */
	private int function(int originalBits, long subkey) {
		// expansie (32 -> 48 bits)
		long source = doExpansion(originalBits);
		Output.printLong(source, "E(r)");

		// bitwise xor met subkey (48^48)
		source = source ^ subkey;
		Output.printLong(source, "XOR");

		int result = 0;
		// De iteratie die de 8 subtituties zal uitvoeren
		// Per blok van 6 bits wordt de subtitutie uitgevoerd met als resultaat
		// 4 bits die aan het totaal resultaat worden toegevoegd
		for (int i = 0; i < 8; i++) {
			// 48 bit waarde in een 64 bit long
			// isoleren van eerste 6 bits:
			// source AND 0000FC0000000000
			// die 6 bits op de laatste 6 plaatsen zetten (48-6=42):
			// >> 42
			byte bitsBlock = (byte) ((source & 0xFC0000000000L) >> 42);

			// de substitutie wordt uitgevoerd met de 6 bits en de index
			// van de s-box
			int substResult = doSubstitution(bitsBlock, i);

			// het resultaat wordt 4 bits naar links geshift zodat
			// zodat er plaats is voor de 4 nieuwe bits
			result = result << 4;

			// de nieuwe bits worden aan het resultaat toegevoegd
			// door middel van een bitwise or
			result = substResult | result;

			// de source wordt 6 plaatsen geshift zodat bij de volgende
			// iteratie de nieuwe 6 bits op de eerste 6 plaatsen staan
			source = source << 6;
		}

		Output.printInt(result, "Subs");

		// permutatie (32 -> 32 bits)
		result = doPermutation(result);
		Output.printInt(result, "Perm");

		return result;
	}

	private byte doSubstitution(byte bits, int box) {
		// De rij wordt uit de bits gehaald.
		// Eerste bit: & 0b100000 om te isoleren, 
		//			   >> 4 om op de juiste plaats te zetten
		// Laatste bit: & 0b1 om te isoleren
		// OR: om de 2 resultaat bits bij elkaar te zetten
		byte row = (byte) ((bits & 0b100000) >> 4 | (bits & 0b1));

		// De rij wordt uit de bits gehaald.
		// De 4 middenste bits worden geisoleerd (AND 011110)
		// De 4 bits worden 1 plaats naar rechts gezet zodat ze 
		// op de juiste plaats staan
		byte col = (byte) ((bits & 0b011110) >> 1);

		// haalt de juiste bitwaarde uit de juiste box, rij en kolom
		return Arrays.S_BOXES[box][row][col];
	}

	/*
	 * Input: 64 bits long 
	 * Output: 64 bits long
	 */
	private long doIP(long key) {
		// analoog met Key.doPC1
		long res = 0;
		for (int i = 0; i < Arrays.IP.length; i++) {
			res = (res << 1);
			int shiftAmount = 64 - Arrays.IP[i];
			res = (key >> shiftAmount & 0b1) | res;
		}
		return res;
	}

	/*
	 * Input: 32 bits int 
	 * Output: 48 bits long
	 */
	private long doExpansion(int bits) {
		// analoog met Key.doPC1
		long res = 0;
		for (int i = 0; i < Arrays.E.length; i++) {
			res = (res << 1);
			int shiftAmount = 32 - Arrays.E[i];
			res = (bits >> shiftAmount & 0b1) | res;
		}
		return res;
	}

	/*
	 * Input: 32 bits int 
	 * Output: 32 bits int
	 */
	private int doPermutation(int bits) {
		// analoog met Key.doPC1
		int res = 0;
		for (int i = 0; i < Arrays.P.length; i++) {
			res = (res << 1);
			int shiftAmount = 32 - Arrays.P[i];
			res = (bits >> shiftAmount & 0b1) | res;
		}
		return res;
	}

	/*
	 * Input: 64 bits long
	 * Output: 64 bits long
	 */
	private long doFP(long key) {
		// analoog met Key.doPC1
		long res = 0;
		for (int i = 0; i < Arrays.FP.length; i++) {
			res = (res << 1);
			int shiftAmount = 64 - Arrays.FP[i];
			res = (key >> shiftAmount & 0b1) | res;
		}
		return res;
	}

}
